package org.dam.service.impl;

import cn.hutool.core.date.DateTime;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.Cleanup;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.dam.common.enums.WebsocketNoticeEnum;
import org.dam.common.exception.ClientException;
import org.dam.common.exception.ServiceException;
import org.dam.common.page.PageResponse;
import org.dam.common.utils.FileUtils;
import org.dam.common.utils.SnowFlakeUtil;
import org.dam.controller.WebSocketServer;
import org.dam.entity.*;
import org.dam.entity.request.BackupFileRequest;
import org.dam.service.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.sql.DataSource;
import java.io.File;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.*;

/**
 * @Author dam
 * @create 2024/1/19 13:53
 */
@Slf4j
@Service
public class ClearServiceImpl implements ClearService {

    @Autowired
    private BackupFileHistoryService backupFileHistoryService;
    @Autowired
    private BackupFileService backupFileService;
    @Autowired
    private BackupSourceService backupSourceService;
    @Autowired
    private WebSocketServer webSocketServer;
    @Autowired
    private SnowFlakeUtil snowFlakeUtil;
    @Autowired
    private DataSource dataSource;


    /**
     * 检查数据，删除 无效备份信息 和 已备份文件
     * 什么叫无效？简单来说就是，已备份文件和原文件对应不上，或者说原文件被删除了
     *
     * @param sourceId
     */
    @Override
    public void clearBySourceId(Long sourceId) {
        clearBySourceIdV2(sourceId);
    }

    /**
     * 分批处理
     * 检查数据，删除 无效备份信息 和 已备份文件
     * 什么叫无效？简单来说就是，已备份文件和原文件对应不上，或者说原文件被删除了
     *
     * @param sourceId
     */
    public void clearBySourceIdV1(Long sourceId) {

        long current = 1;
        ClearTask clearTask = new ClearTask();
        clearTask.setId(snowFlakeUtil.nextId());
        // 填充数据源相关信息
        BackupSource source = backupSourceService.getById(sourceId);
        if (source == null) {
            throw new ClientException("所需要清理的数据源不存在");
        }
        clearTask.setClearSourceRoot(source.getRootPath());

        // 存储要删除的文件
        List<Long> removeBackupFileIdList = new ArrayList<>();
        List<String> removeBackupTargetFilePathList = new ArrayList<>();
        BackupFileRequest backupFileRequest = new BackupFileRequest();
        backupFileRequest.setBackupSourceId(sourceId);
        backupFileRequest.setSize(2000L);
        long totalFileNum = -1;
        long finishFileNum = 0;
        ClearStatistic clearStatistic = new ClearStatistic(0);
        while (true) {
            //// 查询数据，监测看哪些文件需要被删除
            // 分页查询出数据，即分批检查，避免数据量太大，占用太多内存
            backupFileRequest.setCurrent(current);
            PageResponse<BackupFile> backupFilePageResponse = backupFileService.pageBackupFile(backupFileRequest,false);
            if (totalFileNum == -1 && backupFilePageResponse.getTotal() != null) {
                totalFileNum = backupFilePageResponse.getTotal();

                Map<String, Object> dataMap = new HashMap<>();
                clearTask.setTotalFileNum(totalFileNum);
                clearTask.setFinishFileNum(0L);
                clearTask.setClearStatus(0);
                clearTask.setClearNumProgress("0.0");
                clearTask.setStartTime(new DateTime());
                clearTask.setClearTime(0L);
                webSocketServer.sendMessage(JSON.toJSONString(dataMap), WebSocketServer.usernameAndSessionMap.get("Admin"));
                notify(WebsocketNoticeEnum.CLEAR_SUCCESS, dataMap);
            }
            if (backupFilePageResponse.getRecords().size() > 0) {
                for (BackupFile backupFile : backupFilePageResponse.getRecords()) {
                    // 获取备份文件的路径
                    // todo 待优化为存储的时候，不存储整一个路径，节省数据库空间，只存储从根目录开始后面的路径，后面获取整个路径再进行拼接
                    String sourceFilePath = backupFile.getSourceFilePath();
                    File sourceFile = new File(sourceFilePath);
                    if (!sourceFile.exists()) {
                        // --if-- 如果原目录该文件已经被删除，则删除
                        removeBackupFileIdList.add(backupFile.getId());
                        removeBackupTargetFilePathList.add(backupFile.getTargetFilePath());
                    }
                }
                // 换一页来检查
                current += 1;
            } else {
                // 查不出数据了，说明检查完了
                break;
            }

            //// 执行删除
            if (removeBackupFileIdList.size() > 0) {
                // 批量删除无效备份文件
                backupFileService.removeByIds(removeBackupFileIdList);
                // 删除无效的已备份文件
                for (String backupTargetFilePath : removeBackupTargetFilePathList) {
                    File removeFile = new File(backupTargetFilePath);
                    if (removeFile.exists()) {
                        boolean delete = FileUtils.recursionDeleteFiles(removeFile, clearStatistic);
                        if (!delete) {
                            throw new ServiceException("文件无法删除");
                        }
                    }
                }
                // 批量删除无效备份文件对应的备份记录
                backupFileHistoryService.removeByFileIds(removeBackupFileIdList);
                removeBackupFileIdList.clear();
                removeBackupTargetFilePathList.clear();
            }

            // 告诉前端，更新清理状态
            finishFileNum += backupFilePageResponse.getRecords().size();
            Map<String, Object> dataMap = new HashMap<>();
            clearTask.setFinishFileNum(finishFileNum);
            clearTask.setClearStatus(1);
            clearTask.setFinishDeleteFileNum(clearStatistic.finishDeleteFileNum);
            setClearProgress(clearTask, dataMap);
            notify(WebsocketNoticeEnum.CLEAR_PROCESS, dataMap);
        }

        // 清理成功
        Map<String, Object> dataMap = new HashMap<>();
        clearTask.setFinishFileNum(finishFileNum);
        clearTask.setClearStatus(2);
        clearTask.setFinishDeleteFileNum(clearStatistic.finishDeleteFileNum);
        setClearProgress(clearTask, dataMap);
        notify(WebsocketNoticeEnum.CLEAR_SUCCESS, dataMap);
    }

    /**
     * 流式处理
     * 检查数据，删除 无效备份信息 和 已备份文件
     * 什么叫无效？简单来说就是，已备份文件和原文件对应不上，或者说原文件被删除了
     *
     * @param sourceId
     */
    @SneakyThrows
    public void clearBySourceIdV2(Long sourceId) {
        // 获取 dataSource Bean 的连接
        @Cleanup Connection conn = dataSource.getConnection();
        @Cleanup Statement stmt = conn.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
        stmt.setFetchSize(Integer.MIN_VALUE);

        long start = System.currentTimeMillis();
        // 查询sql，只查询关键的字段
        String sql = "SELECT id,source_file_path,target_file_path FROM backup_file where backup_source_id = " + sourceId;
        @Cleanup ResultSet rs = stmt.executeQuery(sql);
        loopResultSetProcessClear(rs, sourceId);
        log.info("流式清理花费时间：{} s ", (System.currentTimeMillis() - start) / 1000);
    }

    /**
     * 循环读取，每次读取一行数据进行处理
     *
     * @param rs
     * @param sourceId
     * @return
     */
    @SneakyThrows
    private Long loopResultSetProcessClear(ResultSet rs, Long sourceId) {
        // 填充数据源相关信息
        BackupSource source = backupSourceService.getById(sourceId);
        if (source == null) {
            throw new ClientException("所需要清理的数据源不存在");
        }
        // 中途用来存储需要删除的文件信息
        List<Long> removeBackupFileIdList = new ArrayList<>();
        List<String> removeBackupTargetFilePathList = new ArrayList<>();
        // 查询文件总数
        long totalFileNum = backupFileService.count(Wrappers.query(new BackupFile()).eq("backup_source_id", sourceId));
        // 已经扫描的文件数量
        long finishFileNum = 0;
        ClearStatistic clearStatistic = new ClearStatistic(0);
        long second = System.currentTimeMillis() / 1000;
        long curSecond;

        // 发送消息通知前端 清理正式开始
        ClearTask clearTask = ClearTask.builder()
                .id(snowFlakeUtil.nextId())
                .clearSourceRoot(source.getRootPath())
                .totalFileNum(totalFileNum)
                .finishFileNum(0L)
                .clearStatus(0)
                .clearNumProgress("0.0")
                .startTime(new DateTime())
                .clearTime(0L)
                .build();
        Map<String, Object> dataMap = new HashMap<>();
        dataMap.put("clearTask", clearTask);
        notify(WebsocketNoticeEnum.CLEAR_START, dataMap);

        // 每次获取一行数据进行处理，rs.next()如果有数据返回true，否则返回false
        while (rs.next()) {
            // 获取数据中的属性
            long fileId = rs.getLong("id");
            String sourceFilePath = rs.getString("source_file_path");
            String targetFilePath = rs.getString("target_file_path");

            // 所扫描的文件数量+1
            finishFileNum++;

            // 获取备份文件的路径
            File sourceFile = new File(sourceFilePath);
            if (!sourceFile.exists()) {
                // --if-- 如果原目录该文件已经被删除，则删除
                removeBackupFileIdList.add(fileId);
                removeBackupTargetFilePathList.add(targetFilePath);
            }

            if (removeBackupFileIdList.size() >= 2000) {
                clear(removeBackupFileIdList, removeBackupTargetFilePathList, clearStatistic);
            }

            curSecond = System.currentTimeMillis() / 1000;
            if (curSecond > second) {
                second = curSecond;

                // 告诉前端，更新清理状态
                clearTask.setFinishFileNum(finishFileNum);
                clearTask.setClearStatus(1);
                clearTask.setFinishDeleteFileNum(clearStatistic.finishDeleteFileNum);
                setClearProgress(clearTask, dataMap);
                notify(WebsocketNoticeEnum.CLEAR_PROCESS, dataMap);
            }
        }

        // 循环结束之后，再清理一次，避免文件数没有到达清理批量导致清理失败
        clear(removeBackupFileIdList, removeBackupTargetFilePathList, clearStatistic);

        // 告诉前端，清理成功
        clearTask.setFinishFileNum(finishFileNum);
        clearTask.setClearStatus(2);
        clearTask.setFinishDeleteFileNum(clearStatistic.finishDeleteFileNum);
        setClearProgress(clearTask, dataMap);
        notify(WebsocketNoticeEnum.CLEAR_SUCCESS, dataMap);

        return 0L;
    }

    /**
     * 执行清理
     * @param removeBackupFileIdList
     * @param removeBackupTargetFilePathList
     * @param clearStatistic
     */
    private void clear(List<Long> removeBackupFileIdList, List<String> removeBackupTargetFilePathList, ClearStatistic clearStatistic) {
        // 批量删除无效备份文件
        backupFileService.removeByIds(removeBackupFileIdList);
        // 删除无效的已备份文件
        for (String backupTargetFilePath : removeBackupTargetFilePathList) {
            File removeFile = new File(backupTargetFilePath);
            if (removeFile.exists()) {
                boolean delete = FileUtils.recursionDeleteFiles(removeFile, clearStatistic);
                if (!delete) {
                    throw new ServiceException("文件无法删除");
                }
            }
        }
        // 批量删除无效备份文件对应的备份记录
        backupFileHistoryService.removeByFileIds(removeBackupFileIdList);
        removeBackupFileIdList.clear();
        removeBackupTargetFilePathList.clear();
    }

    /**
     * 发送通知给前端
     *
     * @param noticeEnum
     * @param dataMap
     */
    private void notify(WebsocketNoticeEnum noticeEnum, Map<String, Object> dataMap) {
        dataMap.put("code", noticeEnum.getCode());
        dataMap.put("message", noticeEnum.getDetail());
        webSocketServer.sendMessage(JSON.toJSONString(dataMap), WebSocketServer.usernameAndSessionMap.get("Admin"));
    }

    /**
     * 设置清理进度
     *
     * @param clearTask
     * @param dataMap
     */
    private void setClearProgress(ClearTask clearTask, Map<String, Object> dataMap) {
        clearTask.setClearNumProgress(String.format("%.1f", clearTask.getFinishFileNum() * 100.0 / clearTask.getTotalFileNum()));
        clearTask.setClearTime(System.currentTimeMillis() - clearTask.getStartTime().getTime());
        dataMap.put("clearTask", clearTask);
    }

}
